/*
Copyright SecureKey Technologies Inc. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/

package endpoint

import (
	"time"

	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/providers/ichain"
	commtls "git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/util/comm/tls"
	"github.com/pkg/errors"
	"github.com/tjfoc/gmsm/gmtls"
)

// EndpointConfigOptions represents EndpointConfig interface with overridable interface functions
// if a function is not overridden, the default EndpointConfig implementation will be used.
type EndpointConfigOptions struct {
	timeout
	consensersConfig
	consenserConfig
	inodesConfig
	inodeConfig
	networkConfig
	networkInodes
	chainConfig
	chainInodes
	chainConsensers
	tlsCACertPool
	tlsClientCerts
	cryptoConfigPath
}

type applier func()
type predicate func() bool
type setter struct{ isSet bool }

// timeout interface allows to uniquely override EndpointConfig interface's Timeout() function
type timeout interface {
	Timeout(ichain.TimeoutType) time.Duration
}

// consensersConfig interface allows to uniquely override EndpointConfig interface's ConsensersConfig() function
type consensersConfig interface {
	ConsensersConfig() []ichain.ConsenserConfig
}

// consenserConfig interface allows to uniquely override EndpointConfig interface's ConsenserConfig() function
type consenserConfig interface {
	ConsenserConfig(name string) (*ichain.ConsenserConfig, bool, bool)
}

// inodesConfig interface allows to uniquely override EndpointConfig interface's InodesConfig() function
type inodesConfig interface {
	InodesConfig(org string) ([]ichain.InodeConfig, bool)
}

// inodeConfig interface allows to uniquely override EndpointConfig interface's InodeConfig() function
type inodeConfig interface {
	InodeConfig(nameOrURL string) (*ichain.InodeConfig, bool)
}

// networkConfig interface allows to uniquely override EndpointConfig interface's NetworkConfig() function
type networkConfig interface {
	NetworkConfig() *ichain.NetworkConfig
}

// networkInodes interface allows to uniquely override EndpointConfig interface's NetworkInodes() function
type networkInodes interface {
	NetworkInodes() []ichain.NetworkInode
}

// chainConfig interface allows to uniquely override EndpointConfig interface's ChainConfig() function
type chainConfig interface {
	ChainConfig(name string) *ichain.ChainEndpointConfig
}

// chainInodes interface allows to uniquely override EndpointConfig interface's ChainInodes() function
type chainInodes interface {
	ChainInodes(name string) []ichain.ChainInode
}

// chainConsensers interface allows to uniquely override EndpointConfig interface's ChainConsensers() function
type chainConsensers interface {
	ChainConsensers(name string) []ichain.ConsenserConfig
}

// tlsCACertPool interface allows to uniquely override EndpointConfig interface's TLSCACertPool() function
type tlsCACertPool interface {
	TLSCACertPool() commtls.CertPool
}

// tlsClientCerts interface allows to uniquely override EndpointConfig interface's TLSClientCerts() function
type tlsClientCerts interface {
	TLSClientCerts() []gmtls.Certificate
}

// cryptoConfigPath interface allows to uniquely override EndpointConfig interface's CryptoConfigPath() function
type cryptoConfigPath interface {
	CryptoConfigPath() string
}

// BuildConfigEndpointFromOptions will return an EndpointConfig instance pre-built with Optional interfaces
// provided in ichainsdk's WithEndpointConfig(opts...) call
func BuildConfigEndpointFromOptions(opts ...interface{}) (ichain.EndpointConfig, error) {
	// build a new EndpointConfig with overridden function implementations
	c := &EndpointConfigOptions{}
	for i, option := range opts {
		logger.Debugf("option %d: %#v", i, option)
		err := setEndpointConfigWithOptionInterface(c, option)
		if err != nil {
			return nil, err
		}
	}

	return c, nil
}

// UpdateMissingOptsWithDefaultConfig will verify if any functions of the EndpointConfig were not updated with ichainsdk's
// WithConfigEndpoint(opts...) call, then use default EndpointConfig interface for these functions instead
func UpdateMissingOptsWithDefaultConfig(c *EndpointConfigOptions, d ichain.EndpointConfig) ichain.EndpointConfig {
	s := &setter{}

	s.set(c.timeout, nil, func() { c.timeout = d })
	s.set(c.consensersConfig, nil, func() { c.consensersConfig = d })
	s.set(c.consenserConfig, nil, func() { c.consenserConfig = d })
	s.set(c.inodesConfig, nil, func() { c.inodesConfig = d })
	s.set(c.inodeConfig, nil, func() { c.inodeConfig = d })
	s.set(c.networkConfig, nil, func() { c.networkConfig = d })
	s.set(c.networkInodes, nil, func() { c.networkInodes = d })
	s.set(c.chainConfig, nil, func() { c.chainConfig = d })
	s.set(c.chainInodes, nil, func() { c.chainInodes = d })
	s.set(c.chainConsensers, nil, func() { c.chainConsensers = d })
	s.set(c.tlsCACertPool, nil, func() { c.tlsCACertPool = d })
	s.set(c.tlsClientCerts, nil, func() { c.tlsClientCerts = d })
	s.set(c.cryptoConfigPath, nil, func() { c.cryptoConfigPath = d })

	return c
}

// IsEndpointConfigFullyOverridden will return true if all of the argument's sub interfaces is not nil
// (ie EndpointConfig interface not fully overridden)
func IsEndpointConfigFullyOverridden(c *EndpointConfigOptions) bool {
	return !anyNil(c.timeout, c.consensersConfig, c.consenserConfig, c.inodesConfig, c.inodeConfig, c.networkConfig,
		c.networkInodes, c.chainConfig, c.chainInodes, c.chainConsensers, c.tlsCACertPool, c.tlsClientCerts, c.cryptoConfigPath)
}

// will override EndpointConfig interface with functions provided by o (option)
func setEndpointConfigWithOptionInterface(c *EndpointConfigOptions, o interface{}) error {
	s := &setter{}

	s.set(c.timeout, func() bool { _, ok := o.(timeout); return ok }, func() { c.timeout = o.(timeout) })
	s.set(c.consensersConfig, func() bool { _, ok := o.(consensersConfig); return ok }, func() { c.consensersConfig = o.(consensersConfig) })
	s.set(c.consenserConfig, func() bool { _, ok := o.(consenserConfig); return ok }, func() { c.consenserConfig = o.(consenserConfig) })
	s.set(c.inodesConfig, func() bool { _, ok := o.(inodesConfig); return ok }, func() { c.inodesConfig = o.(inodesConfig) })
	s.set(c.inodeConfig, func() bool { _, ok := o.(inodeConfig); return ok }, func() { c.inodeConfig = o.(inodeConfig) })
	s.set(c.networkConfig, func() bool { _, ok := o.(networkConfig); return ok }, func() { c.networkConfig = o.(networkConfig) })
	s.set(c.networkInodes, func() bool { _, ok := o.(networkInodes); return ok }, func() { c.networkInodes = o.(networkInodes) })
	s.set(c.chainConfig, func() bool { _, ok := o.(chainConfig); return ok }, func() { c.chainConfig = o.(chainConfig) })
	s.set(c.chainInodes, func() bool { _, ok := o.(chainInodes); return ok }, func() { c.chainInodes = o.(chainInodes) })
	s.set(c.chainConsensers, func() bool { _, ok := o.(chainConsensers); return ok }, func() { c.chainConsensers = o.(chainConsensers) })
	s.set(c.tlsCACertPool, func() bool { _, ok := o.(tlsCACertPool); return ok }, func() { c.tlsCACertPool = o.(tlsCACertPool) })
	s.set(c.tlsClientCerts, func() bool { _, ok := o.(tlsClientCerts); return ok }, func() { c.tlsClientCerts = o.(tlsClientCerts) })
	s.set(c.cryptoConfigPath, func() bool { _, ok := o.(cryptoConfigPath); return ok }, func() { c.cryptoConfigPath = o.(cryptoConfigPath) })

	if !s.isSet {
		return errors.Errorf("option %#v is not a sub interface of EndpointConfig, at least one of its functions must be implemented.", o)
	}

	return nil
}

// needed to avoid meta-linter errors (too many if conditions)
func (o *setter) set(current interface{}, check predicate, apply applier) {
	if current == nil && (check == nil || check()) {
		apply()
		o.isSet = true
	}
}

// will verify if any of objs element is nil, also needed to avoid meta-linter errors
func anyNil(objs ...interface{}) bool {
	for _, p := range objs {
		if p == nil {
			return true
		}
	}
	return false
}
